package com.lw.smartquiz.assess;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lw.smartquiz.common.ErrorCode;
import com.lw.smartquiz.constant.AIConstant;
import com.lw.smartquiz.exception.BusinessException;
import com.lw.smartquiz.exception.ThrowUtils;
import com.lw.smartquiz.manager.AIManager;
import com.lw.smartquiz.model.dto.question.QuestionContentDTO;
import com.lw.smartquiz.model.dto.useranswer.AIAssessDTO;
import com.lw.smartquiz.model.entity.App;
import com.lw.smartquiz.model.entity.Question;
import com.lw.smartquiz.model.entity.UserAnswer;
import com.lw.smartquiz.model.enums.AppTypeEnum;
import com.lw.smartquiz.model.enums.ScoringStrategyEnum;
import com.lw.smartquiz.model.vo.QuestionVO;
import com.lw.smartquiz.service.QuestionService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * AI 评判的测评类应用的评分策略（AI 判题得到的结果不是出题者设定好的评定结果，不对应scoringResult表中的记录）
 */
@Slf4j
@AssessStrategyConfig(appType = AppTypeEnum.TEST, strategyType = ScoringStrategyEnum.AI)
public class AITestAssessStrategy implements AssessStrategy {
    @Resource
    private QuestionService questionService;
    @Resource
    private AIManager aiManager;
    @Resource
    private RedissonClient redissonClient;
    /**
     * 分布式锁的前缀
     */
    private static final String AI_ASSESS_LOCK = "ai:assess:";
    /**
     * AI评判结果的缓存
     */
    private static final Cache<String, String> cacheOfAI = Caffeine
            .newBuilder()
            //设置初始的key的个数容量
            .initialCapacity(1024)
            //设置一天没有被访问就过期
            .expireAfterAccess(1, TimeUnit.DAYS)
            .build();

    /**
     * 拼接 AI 判题的用户提示词
     * @param app
     * @param questionContentDTOList
     * @param choices
     * @return
     */
    private String makeAIAssessUserMessage(App app, List<QuestionContentDTO> questionContentDTOList, List<String> choices) {
        // 创建StringBuilder实例
        StringBuilder userMessage = new StringBuilder();
        // 拼接应用信息
        userMessage.append(app.getAppName()).append("\n");
        userMessage.append("【【").append(app.getAppDesc()).append("】】").append("\n");
        // 组装题目和用户回答的列表
        List<AIAssessDTO> aiAssessDTOList = new ArrayList<>();
        for (int i = 0; i < questionContentDTOList.size(); i++) {
            QuestionContentDTO questionContentDTO = questionContentDTOList.get(i);
            AIAssessDTO aiAssessDTO = new AIAssessDTO();
            //题目
            aiAssessDTO.setTitle(questionContentDTO.getTitle());
            //for循环根据用户的选择的字母（A、B）找到对应的选项内容
            for (QuestionContentDTO.Option option : questionContentDTO.getOptions()) {
                if (choices.get(i).equals(option.getKey())) {
                    aiAssessDTO.setAnswer(option.getValue());
                    break;
                }
            }
            ThrowUtils.throwIf(StrUtil.isEmpty(aiAssessDTO.getAnswer()), ErrorCode.OPERATION_ERROR, "用户回答了不存在的选项");
            aiAssessDTOList.add(aiAssessDTO);
        }
        // 拼接题目和答案列表
        userMessage.append(JSONUtil.toJsonStr(aiAssessDTOList));
        return userMessage.toString();
    }

    @Override
    public UserAnswer doScore(List<String> choices, App app) {
        // 根据app获取题目
        Long appId = app.getId();
        //缓存中有就使用缓存返回
        String choicesJson = JSONUtil.toJsonStr(choices);
        String cacheKey = this.buildCacheKey(appId, choicesJson);
        String cacheResultJson = cacheOfAI.getIfPresent(cacheKey);
        if(StrUtil.isNotBlank(cacheResultJson)) {
            UserAnswer userAnswer = JSONUtil.toBean(cacheResultJson, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(choicesJson);
            return userAnswer;
        }
        //没有缓存就获取锁，防止缓存击穿
        RLock lock = redissonClient.getLock(AI_ASSESS_LOCK + cacheKey);
        try {
            //尝试获取锁，失败直接返回false，并开启看门狗机制，默认30秒有效期，每隔10秒，线程还持有这个锁，就会自动续期
            boolean success = lock.tryLock();
            //获取到锁就去调用AI判题
            if(success) {
                //所有的结果属性集
                Question question = questionService.lambdaQuery().eq(Question::getAppId, appId).one();
                QuestionVO questionVO = QuestionVO.objToVo(question);
                List<QuestionContentDTO> questionList = questionVO.getQuestionContent();

                // 拼接 AI 判题的用户提示词
                String userMessage = makeAIAssessUserMessage(app, questionList, choices);
                // 调用智谱AI进行判题
//        log.debug("调用智谱AI进行判题，用户提示词：\n{}", userMessage);
//        log.debug("调用智谱AI进行判题，系统提示词：\n{}", AIConstant.AI_ASSESS_SYSTEM_PROMPT);
                String result = aiManager.syncStableRequest(AIConstant.AI_ASSESS_SYSTEM_PROMPT, userMessage);
//        log.debug("调用智谱AI进行判题，返回结果：\n{}", result);
                // 对AI返回结果进行处理
                int start = result.indexOf('{');
                int end = result.lastIndexOf('}');
                String resultJson = result.substring(start, end + 1);
                // json转对象
                UserAnswer userAnswer = JSONUtil.toBean(resultJson, UserAnswer.class);
                // 校验AI返回结果
                ThrowUtils.throwIf(StrUtil.isBlank(userAnswer.getResultName())
                                || StrUtil.isBlank(userAnswer.getResultDesc()),
                        ErrorCode.OPERATION_ERROR, "AI返回结果格式错误");

                //AI返回结果存入缓存
                cacheOfAI.put(cacheKey, resultJson);

                // 填充用户答案
                userAnswer.setAppId(appId);
                userAnswer.setAppType(app.getAppType());
                userAnswer.setScoringStrategy(app.getScoringStrategy());
                userAnswer.setChoices(choicesJson);
                //userAnswer.setResultId();// AI 生成的结果不对应scoringResult表中的记录

                return userAnswer;
            }else {
                //获取不到锁，等待几秒钟，再次尝试走缓存和重新获取锁
                log.debug("获取锁失败，等待几秒钟再次尝试");
                Thread.sleep(2000);
                return this.doScore(choices, app);
            }
        } catch (InterruptedException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        } finally {
            //最后要释放锁，释放的锁必须是由本线程获取的
            if(lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 根据应用id和用户的选择，通过md5摘要，构建缓存的key
     * @param appId
     * @param choicesJson
     * @return
     */
    private String buildCacheKey(Long appId, String choicesJson){
        return DigestUtil.md5Hex(appId + ":" + choicesJson);
    }
}
